package com.atguigu.com.gmall.index.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

@Component
public class DistributedLock {

    @Autowired
    StringRedisTemplate redisTemplate;

    private Timer timer;
    /*
    尝试获取锁 原子操作
     */
    public Boolean tryLock(String lockName,String uuid,Integer expire){
        //lua 脚本 exists判断key是否存在 存在返回1 不存在返回0
        //hexists 判断hash表是否存在
        //expire设置key的过期时间
        //如果当前锁(key)存在 但是key value值一样 则时可重入锁 那么值加1 同时从新设置过期时间
        String script = "if redis.call('exists',KEYS[1])==0 or redis.call('hexists',KEYS[1],ARGV[1])==1 " +
                "then " +
                "    redis.call('hincrby',KEYS[1],AVGR[1],1)" +
                "    redis.call('expire',KEYS[1],AVGR[2])" +
                "    return 1" +
                "else" +
                "    return 0" +
                "end";
        Boolean flag = redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, expire.toString());
        if (!flag){
            try {
                Thread.sleep(80);
                //睡眠80毫秒 重试
                tryLock(lockName,uuid,expire);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            //加锁成功后每隔多久自动续期一次
            renewExpire(lockName,uuid,expire);
        }
        return true;
    }

    //解锁 删除锁
    public void unLock(String lockName,String uuid){
        //判断是否是自己的锁 如果不是 则保存
        //如果是自己的锁 那么减1之后值为0的话 则删除锁 释放 否则还有锁没有释放完毕
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " +
                "then" +
                "    return nil" +
                "elseif redis.call('hincrby'',KEYS[1],ARGV[1],-1)==0" +
                "then" +
                "    return redis.call('del',KEYS[1])" +
                "else" +
                "    return 0" +
                "end";
        Long flag = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid);
        if (flag==null){
            throw new RuntimeException("你没有锁");
        }else if (flag==1){
            //解除自动续期
            timer.cancel();
        }
    }
    //自动续期
    private void renewExpire(String lockName, String uuid, Integer expire){
        //如果锁存在 则设置锁的过期时间
        String scriot = "if redis.call('hexists',KEYS[1],ARGV[1])==1" +
                "then" +
                "    return redis.call('expire',KEYS[1],ARVG[2])" +
                "else" +
                "    return 0" +
                "end";
        this.timer = new Timer();
        //参数1 线程方法
        //参数2 线程第一次运行的时间
        //参数3 线程之后每隔多久运行一次
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                redisTemplate.execute(new DefaultRedisScript<>(scriot,Boolean.class),Arrays.asList(lockName),uuid,expire);
            }
        },expire*1000/3,expire * 1000/3);
    }
}
